# --coding: utf-8 --
import requests
import json
import time
from datetime import datetime
from tqsdk import TqApi, TqAuth, TargetPosTask, TqKq
from tqsdk.ta import ATR
from chinese_calendar import is_workday

allstatus = {}


def saveStates(symbol, state):
    global allstatus
    print('保存成功')
    allstatus[symbol] = state
    json.dump(allstatus, open("turtle_state.json", "w"))  # 保存数据


def send_msg(content):
    """钉钉消息提醒模块"""
    webhook = "https://oapi.dingtalk.com/robot/send?access_token=db46b7ef6f95fb96b1bc814254d4a3ffaf9b1f6e585aacd9351783ed4764da09"

    # 钉钉安全规则将 天勤量化 设为关键字
    msg = {"msgtype": "text",
           "text": {"content": "{}\n{}\n".format("量化\n" + str(content),
                                                 datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}, }
    headers = {"content-type": "application/json;charset=utf-8"}
    body = json.dumps(msg)
    requests.post(webhook, data=body, headers=headers)


class Turtle:
    def __init__(self, symbol, account=None, auth=None, use_money=100000, donchian_channel_open_position=20,
                 donchian_channel_stop_profit=10,
                 atr_day_length=20, max_risk_ratio=0.5):
        self.account = account  # 交易账号
        self.auth = auth  # 信易账户
        self.symbol = symbol  # 合约代码
        # 唐奇安通道的天数周期(开仓)
        self.donchian_channel_open_position = donchian_channel_open_position
        # 唐奇安通道的天数周期(止盈)
        self.donchian_channel_stop_profit = donchian_channel_stop_profit
        self.atr_day_length = atr_day_length  # ATR计算所用天数
        self.max_risk_ratio = max_risk_ratio  # 最高风险度
        self.state = {
            "position": 0,  # 本策略净持仓数(正数表示多头，负数表示空头，0表示空仓)
            "last_price": float("nan"),  # 上次调仓价
            "last_result": False
        }

        self.money = use_money
        self.n = 0  # 平均真实波幅(N值)
        self.unit = 0  # 买卖单位
        self.donchian_channel_high = 0  # 唐奇安通道上轨
        self.donchian_channel_low = 0  # 唐奇安通道下轨

        self.api = TqApi(self.account, auth=self.auth)
        self.quote = self.api.get_quote(self.symbol)
        # 由于ATR是路径依赖函数，因此使用更长的数据序列进行计算以便使其值稳定下来
        kline_length = max(donchian_channel_open_position + 1,
                           donchian_channel_stop_profit + 1, atr_day_length * 5)
        self.klines = self.api.get_kline_serial(
            self.symbol, 24 * 60 * 60, data_length=kline_length)
        self.account = self.api.get_account()
        self.target_pos = TargetPosTask(self.api, self.symbol)

    def recalc_paramter(self):
        # 平均真实波幅(N值)
        self.n = ATR(self.klines, self.atr_day_length)["atr"].iloc[-1]
        # 买卖单位
        self.unit = int((self.money * 0.01) /
                        (self.quote.volume_multiple * self.n))
        # 唐奇安通道上轨：前N个交易日的最高价
        self.donchian_channel_high = max(
            self.klines.high[-self.donchian_channel_open_position - 1:-1])
        # 唐奇安通道下轨：前N个交易日的最低价
        self.donchian_channel_low = min(
            self.klines.low[-self.donchian_channel_open_position - 1:-1])
        send_msg(self.symbol+"唐其安通道上下轨: %f, %f, 今日ATR值: %f, 头寸：%d" %
                 (self.donchian_channel_high, self.donchian_channel_low, self.n,self.unit))
        return True

    def set_position(self, pos):
        self.state["position"] = pos
        self.state[
            "last_price"] = self.quote["last_price"]
        self.target_pos.set_target_volume(self.state["position"])
        saveStates(self.symbol, self.state)

    def try_open(self):
        """开仓策略"""
        while self.state["position"] == 0:
            if isMarketopen():
                self.api.wait_update()
                # 如果产生新k线,则重新计算唐奇安通道及买卖单位
                if self.api.is_changing(self.klines.iloc[-1], "datetime"):
                    self.recalc_paramter()
                if self.api.is_changing(self.quote, "last_price"):
                    print("%s最新价: %.1f" % (self.symbol, self.quote.last_price))
                    # 当前价>唐奇安通道上轨，买入1个Unit；(持多仓)
                    if self.quote.last_price > self.donchian_channel_high:
                        send_msg(self.symbol + "当前价%.1f>唐奇安通道上轨，买入1个Unit(持多仓): %d 手" %(
                                 self.quote.last_price, self.unit))
                        self.set_position(self.state["position"] + self.unit)

                    # 当前价<唐奇安通道下轨，卖出1个Unit；(持空仓)
                    elif self.quote.last_price < self.donchian_channel_low:
                        send_msg(self.symbol + "当前价%.1f<唐奇安通道下轨，卖出1个Unit(持空仓): %d 手" %(
                                 self.quote.last_price, self.unit))
                        self.set_position(self.state["position"] - self.unit)

            else:
                saveStates(self.symbol, self.state)
                return False

    def try_close(self):
        """交易策略"""
        while self.state["position"] != 0:
            if isMarketopen():
                self.api.wait_update()
                if self.api.is_changing(self.quote, "last_price"):
                    print("%s最新价: %.1f" % (self.symbol, self.quote.last_price))
                    if self.state["position"] > 0:  # 持多单
                        # 加仓策略: 如果是多仓且行情最新价在上一次建仓（或者加仓）的基础上又上涨了0.5N，就再加一个Unit的多仓,并且风险度在设定范围内(以防爆仓)
                        if self.quote.last_price >= self.state[
                                "last_price"] + 0.5 * self.n and self.state["position"] <= 4*self.unit:
                            send_msg(self.symbol + "%.1f加仓:加1个Unit的多仓" %
                                     self.quote.last_price)
                            self.set_position(
                                self.state["position"] + self.unit)
                        # 止损策略: 如果是多仓且行情最新价在上一次建仓（或者加仓）的基础上又下跌了2N，就卖出全部头寸止损
                        elif self.quote.last_price <= self.state["last_price"] - 2 * self.n:
                            send_msg(self.symbol + "%.1f止损:卖出全部头寸" %
                                     self.quote.last_price)
                            self.set_position(0)
                            self.state["last_result"] = False
                        # 止盈策略: 如果是多仓且行情最新价跌破了10日唐奇安通道的下轨，就清空所有头寸结束策略,离场
                        if self.quote.last_price <= min(self.klines.low[-self.donchian_channel_stop_profit - 1:-1]):
                            send_msg(self.symbol + "%.1f止盈:清空所有头寸结束策略,离场" %
                                     self.quote.last_price)
                            self.set_position(0)
                            self.state["last_result"] = True

                    elif self.state["position"] < 0:  # 持空单
                        # 加仓策略: 如果是空仓且行情最新价在上一次建仓（或者加仓）的基础上又下跌了0.5N，就再加一个Unit的空仓,并且风险度在设定范围内(以防爆仓)
                        if self.quote.last_price <= self.state[
                                "last_price"] - 0.5 * self.n and self.state["position"] <= 4*self.unit:
                            send_msg(self.symbol + "%.1f加仓:加1个Unit的空仓" %
                                     self.quote.last_price)
                            self.set_position(
                                self.state["position"] - self.unit)
                        # 止损策略: 如果是空仓且行情最新价在上一次建仓（或者加仓）的基础上又上涨了2N，就平仓止损
                        elif self.quote.last_price >= self.state[
                                "last_price"] + 2 * self.n:
                            send_msg(self.symbol + "%.1f止损:卖出全部头寸" %
                                     self.quote.last_price)
                            self.set_position(0)
                            self.state["last_result"] = False

                        # 止盈策略: 如果是空仓且行情最新价升破了10日唐奇安通道的上轨，就清空所有头寸结束策略,离场
                        if self.quote.last_price >= max(self.klines.high[-self.donchian_channel_stop_profit - 1:-1]):
                            send_msg(self.symbol + "%.1f止盈:清空所有头寸结束策略,离场" %
                                     self.quote.last_price)
                            self.set_position(0)
                            self.state["last_result"] = True
            else:
                saveStates(self.symbol, self.state)
                return False

    def strategy(self):
        global allstatus
        """海龟策略"""
        print("等待K线及账户数据...")
        deadline = time.time() + 5
        while not self.recalc_paramter():
            if not self.api.wait_update(deadline=deadline):
                raise Exception("获取数据失败，请确认行情连接正常并已经登录交易账户")
        while True:
            if isMarketopen():
                self.try_open()
                self.try_close()
            else:
                print('保存成功')
                allstatus[self.symbol] = self.state
                json.dump(allstatus, open("turtle_state.json", "w"))  # 保存数据
                return False


# DCE 大连商品交易所
# CZCE  郑州商品交易所  CZCE.MA305:甲醇  CZCE.RM305:菜粕  CZCE.FG305:玻璃
# api = TqApi(account = TqKq(),auth=TqAuth("wzh17796365728", "1980125"))
# api = TqApi(TqAccount("Z中信期货", "121615398", "1624362377"), auth=TqAuth("wzh5728", "Wu1980125"))   快期模拟


def run(obj):
    global allstatus
    turtle = Turtle(symbol=obj, account=TqKq(), auth=TqAuth("wzh5728", "Wu1980125"), donchian_channel_open_position=20,
                    donchian_channel_stop_profit=10,
                    atr_day_length=20, max_risk_ratio=1)
    send_msg(obj+"策略开始运行")
    try:
        # 读取数据: 本策略目标净持仓数,上一次开仓价
        allstatus = json.load(open("turtle_state.json", "r"))
        for items in allstatus.keys():
            print(items)
            if items == turtle.symbol:
                turtle.state = allstatus[turtle.symbol]
    except FileNotFoundError:
        pass
    send_msg(obj+"当前持仓数: %d, 上次调仓价: %f, 上次交易是否盈利：%d" %
             (turtle.state["position"], turtle.state["last_price"], turtle.state["last_result"]))
    try:
        turtle.strategy()
    finally:
        print('finally')
        turtle.api.close()
        allstatus[turtle.symbol] = turtle.state
        json.dump(allstatus, open("turtle_state.json", "w"))  # 保存数据


def waitMarketopen(show=True, waittime=5):
    while True:
        today = datetime.now().date()
        timenow = time.strftime('%H%M%S', time.localtime(time.time()))
        if is_workday(today):
            if int(timenow) > 90005 and int(timenow) < 101455:
                break
            if int(timenow) > 103005 and int(timenow) < 112955:
                break
            if int(timenow) > 133005 and int(timenow) < 145955:
                break
            if int(timenow) > 210005 and int(timenow) < 225955:
                break
        if show:
            if is_workday(today):
                print(str(today) + ' is work day ' + timenow)
            else:
                print(str(today) + ' is weekend ' + timenow)
        time.sleep(waittime)
    if show:
        print("begin!")


def isMarketopen():
    timenow = time.strftime('%H%M%S', time.localtime(time.time()))
    if int(timenow) > 90000 and int(timenow) < 101455:
        return True
    if int(timenow) > 103000 and int(timenow) < 112955:
        return True
    if int(timenow) > 133000 and int(timenow) < 145955:
        return True
    if int(timenow) > 210000 and int(timenow) < 225955:
        return True
    return False


def turtlego(obj):
    while True:
        waitMarketopen()
        run(obj)
